/*
 * UserNet  --  TCP/IP implementation in userspace
 * Copyright (C) 2010  Alex Diban
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include "unet/ip.h"
#include "unet/icmp.h"
#include <stdlib.h>
#include <string.h>
#include "unet/routes.h"
#include "unet/arp.h"
#include "unet/checksum.h"

void
unet_ip_incoming_packet (struct unet_device *ud, const uint8_t *packet, const uint32_t len)
{
  
  /*
  1)Comprobar checksum
  2)Obtener la ip de la interficie por donde nos ha llegado.
  3)Ver si esa ip corresponde con la destiny addr del paquete IP (mirar header)
  4)Si es así: enviar el paquete mediante net_rpc_ip_incoming_packet de port.c
  4.1)Mira si es para ICMP o TCP/UDP
  5)Sino: Enviar el paquete mediante unet_ethernet_send_packet de ether.c a la interfaz de corresponda (tabla routing)
  */
  
  debug_printf("ip","IP: Received an IP packet\n");
  
  
  struct unet_ip_packet * packet_ip;
  packet_ip=(struct unet_ip_packet*) packet;
  
  debug_printf("ip","IP: total size=%u,\n",len);
  debug_printf("ip","IP: size of header=%d\n",packet_ip->header.ihl);  
  debug_printf("ip","IP: size of payload=%d,\n",ntohs(packet_ip->header.tot_len)-packet_ip->header.ihl);
  debug_printf("ip","IP: TTL=%u,\n",packet_ip->header.ttl);
  debug_printf("ip","IP: Protocol=%u,\n",packet_ip->header.protocol);
  
  struct in_addr saddr;
  saddr.s_addr=packet_ip->header.saddr;
  
  struct in_addr daddr;
  daddr.s_addr=packet_ip->header.daddr;
  
  debug_printf("ip","IP: source  addr=%s\n",inet_ntoa(saddr));
  debug_printf("ip","IP: dest    addr=%s\n",inet_ntoa(daddr));
  debug_printf("ip","IP: disp    addr=%s\n",inet_ntoa(ud->ip_addr));
  debug_printf("ip","\n");
  
  
  if (! unet_checksum_verify (packet_ip)){
    debug_printf("ip","IP: Bad checksum\n");   
  }
  else{
    if (packet_ip->header.daddr == ud->ip_addr.s_addr){
	if (packet_ip->header.protocol==0x01){	
	  //Para ICMP, no sale del servidor
	  debug_printf("ip","IP: This packet is for ICMP\n");    
	  unet_icmp_incoming_packet (saddr, packet_ip->payload, ntohs(packet_ip->header.tot_len)-packet_ip->header.ihl);
	}	
	else{
	  unet_rpc_ip_incoming_packet(packet, len);
	}
    }
    else{
      //Miramos la dirección de destino del paquete IP
      struct in_addr addr_des;	
      addr_des.s_addr=packet_ip->header.daddr;
  
      //Miramos la tabla de routing para saber por que interficie enviar el paquete
      struct unet_device *device;
      device=unet_route_find(addr_des);
      
      if (device!=NULL){ 
	debug_printf("ip","IP: forwarding to device id=%d\n",device->id);
	uint8_t mac_addr_d[ETH_ALEN];
	if (device->driver.datalink.get_mac(addr_des,mac_addr_d,device)==0){      
	  device->driver.datalink.send_packet(0x0800, mac_addr_d,device,packet,len);
	}
	else debug_printf("ip","IP: Waiting for ARP REPLY\n");
      }
      else debug_printf("ip","IP: Destination Host Unreachable, maybe you need to add a new route\n");              
    }
  }
  debug_printf("ip","\n");
  debug_printf("ip","\n");
}

/* This function is called when client requested that an outgoing packet has
   to be sent.  */
void
unet_ip_send_packet (const uint8_t *packet, const uint32_t len)
{
  debug_printf("ip","IP: Send request for %u-byte packet\n", len);
  
  /*
  1)Obtener la interficie (Device) a la que enviar el paquete (por tabla de routing + ip destino)
  2)Obtener la ip de la interficie 
  3)meterla dentro del header del paquete ip como src addr
  3)Calcular el checksum del paquete y meterlo en el header (crear un nuevo metodo para ello)
  4)Buscar la Mac de destino (ARP)
  5)Enviar el paquete mediante unet_ethernet_send_packet de ether.c
  */
  
  struct unet_ip_packet * packet_ip;
  packet_ip=(struct unet_ip_packet*) packet;
 
  //Miramos la dirección de destino del paquete IP
  struct in_addr addr_des;
  addr_des.s_addr=packet_ip->header.daddr;
  
  //Miramos la tabla de routing para saber por que interficie enviar el paquete
  struct unet_device *device;
  device=unet_route_find(addr_des);
  
  
  
  if (device!=NULL){ 
	debug_printf("ip","IP: forwarding to device id=%d\n",device->id);
	
	//Ponemos la Ip de la interfice como ip origen del paquete IP
	packet_ip->header.saddr=device->ip_addr.s_addr; 
  
	//Checksum
	unet_checksum_set(packet_ip);
	
	uint8_t mac_addr_d[ETH_ALEN];
	if (device->driver.datalink.get_mac(addr_des,mac_addr_d,device)==0){ 

	  printf("DEBUG: mac_addr_d, %02x:%02x:%02x:%02x:%02x:%02x"
		 ,mac_addr_d[0],mac_addr_d[1],mac_addr_d[2], mac_addr_d[3],mac_addr_d[4],mac_addr_d[5]);

	  device->driver.datalink.send_packet(0x0800,mac_addr_d,device,packet,len);
	}
	else{
	  debug_printf("ip","IP: ARP REPLY TIMEOUT\n");
	}
  }
  else debug_printf("ip","IP: Destination Host Unreachable, maybe you need to add a new route\n");              

}

/*Se encarga de encapsular los paquetes ICMP */
void
unet_ip_icmp_send_packet(struct in_addr ip_addr,void * buff,size_t len)
{

  debug_printf("ip","IP: received packet of %ul bytes from ICMP \n", (unsigned int)len);
    
  struct unet_ip_packet * packet_ip;
  uint32_t ip_len =sizeof(packet_ip->header)+len;
  packet_ip= malloc (ip_len);

  //Damos valores a los campos de la cabecera ip
  packet_ip->header.version=0x04;
  packet_ip->header.ihl=IP_HEADER_SIZE;
  
  packet_ip->header.tos=0;
  packet_ip->header.tot_len=htons(ip_len);
  
  packet_ip->header.id=0;	
  packet_ip->header.frag_off=0;	
  
  
  //Valor por defecto
  packet_ip->header.ttl=64;	
  
  packet_ip->header.protocol=0x01;

  //se asigna luego en unet_ip_send_packet
  packet_ip->header.saddr=0;
  //addr de destino
  packet_ip->header.daddr=ip_addr.s_addr;	
  //se calcula en el servidor, una vez asignada la saddr
  packet_ip->header.check=0; 	

  //Copia el paquete ICMP en el payload de IP
  memcpy(packet_ip->payload,buff,len);

  //Envia el paquete IP
  unet_ip_send_packet((const uint8_t*)packet_ip, ip_len);
  
  free(packet_ip);
  
}
